#include "f_core.h"
#include "f_ops2.h"

#include "debug.h"
#include "inspect.h"

/** представить кортеж в "компактном" виде, удобным для reverse,chain,map,filter
    ( 1 2 3 4 5 6 7 8 9 10 ) => ( 1 ( 2 3 4 5 ) (6 7 8 9) 10 )
    то есть : первый элемент, от второго до медианы, от медианы до предпоследнего, последний элемент
*/
int
tupleObjCompact(Tcl_Interp *interp,Tcl_Obj *tupleObj)
{
    (void)interp;
    consCollapse(TUPLE(tupleObj)->head,TUPLE(tupleObj)->tail);
    return TCL_OK;
}

int
mapObjCmd(ClientData data,Tcl_Interp *interp,int objc,Tcl_Obj *const objv[])
{
    Tcl_Obj *result;
    Cons *cons;
    
    (void)data;
    if (objc==1) {
        Tcl_SetObjResult(interp,tupleObjNew());
        return TCL_OK;
    } else if (objc==2) {
        Tcl_SetObjResult(interp,objv[1]);
        return TCL_OK;
    }
    Tcl_Obj *tupleObj;
    tupleObj=objv[objc-1];
    SHOULD(tupleObj!=NULL);
    if (tupleObj->typePtr==lazyType) {
        lazyObjExec(tupleObj,1);
    }
    SHOULD(tupleObj->typePtr==tupleType || Tcl_ConvertToType(interp,tupleObj,tupleType)==TCL_OK,return TCL_ERROR);
    if (tupleObjCompact(interp,tupleObj)!=TCL_OK) return TCL_ERROR;
    result=tupleObjNew();
    for(cons=TUPLE(tupleObj)->head;cons!=NULL;cons=cons->next) {
        if (cons->obj==NULL) continue;
        // TODO: map f [ map g x ] => map f [ g x ]
        if (cons->obj->typePtr==tupleType || cons->obj->typePtr==lazyType) {
            tupleObjPut(interp,result,lazyObjNew(interp,NULL,objc-1,objv,cons->obj,NULL));
        } else {
            tupleObjPut(interp,result,lazyObjNew(interp,NULL,objc-2,objv+1,cons->obj,NULL));
        }
    }
    Tcl_SetObjResult(interp,result);
    return TCL_OK;
}
int
filterObjCmd(ClientData data,Tcl_Interp *interp,int objc,Tcl_Obj *const objv[])
{
    Tcl_Obj *tupleObj;
    Tcl_Obj *result;
    Cons *cons;
    
    (void)data;
    if (objc==1) {
        Tcl_SetObjResult(interp,tupleObjNew());
        return TCL_OK;
    } else if (objc==2) {
        Tcl_SetObjResult(interp,objv[1]);
        return TCL_OK;
    }
    tupleObj=objv[objc-1];
    SHOULD(tupleObj!=NULL);
    if (tupleObj->typePtr==lazyType) {
        lazyObjExec(tupleObj,1);
    }
    SHOULD(tupleObj->typePtr==tupleType || Tcl_ConvertToType(interp,tupleObj,tupleType)==TCL_OK,return TCL_ERROR);
    if (tupleObjCompact(interp,tupleObj)!=TCL_OK) return TCL_ERROR;
    result=tupleObjNew();
    for(cons=TUPLE(tupleObj)->head;cons!=NULL;cons=cons->next) {
        if (cons->obj==NULL) continue;
        // TODO: filter f [ filter g x ]  => filter f [] g x
        if (cons->obj->typePtr==tupleType || cons->obj->typePtr==lazyType) {
            tupleObjPut(interp,result,lazyObjNew(interp,NULL,objc-1,objv,cons->obj,NULL));
        } else {
            Tcl_Obj *lazyObj;
            Tcl_Obj *cmdName;
            lazyObj=lazyObjNew(interp,NULL,objc-1,objv,cons->obj,NULL);
            cmdName=Tcl_NewStringObj("T",1);
            Tcl_IncrRefCount(cmdName);
            Tcl_ListObjReplace(interp,LAZY(lazyObj)->cmd,0,1,1,&cmdName);
            Tcl_DecrRefCount(cmdName);
            tupleObjPut(interp,result,lazyObj);
        }
    }
    Tcl_SetObjResult(interp,result);
    return TCL_OK;
}

int
reverseObjCmd(ClientData data,Tcl_Interp *interp,int objc,Tcl_Obj *const objv[])
{
    (void)data;
    if (objc==1) {
        Tcl_SetObjResult(interp,tupleObjNew());
        return TCL_OK;
    }
    Tcl_Obj *tupleObj;
    tupleObj=objv[objc-1];
    SHOULD(tupleObj!=NULL);
    if (tupleObj->typePtr==lazyType)
        lazyObjExec(tupleObj,1);
    SHOULD(tupleObj->typePtr==tupleType || Tcl_ConvertToType(interp,tupleObj,tupleType)==TCL_OK,return TCL_ERROR);
    if (tupleObjCompact(interp,tupleObj)!=TCL_OK) {
        
        return TCL_ERROR;
    }
    Tcl_Obj *result;
    Cons *cons;
    result=tupleObjNew();
    for(cons=TUPLE(tupleObj)->head;cons!=NULL;cons=cons->next) {
        if (cons->obj==NULL) continue;
        // TODO: reverse f [ reverse f x ] => x
        if (cons->obj->typePtr==tupleType || cons->obj->typePtr==lazyType) {
            tupleObjPush(interp,result,lazyObjNew(interp,NULL,objc-1,objv,cons->obj,NULL));
        } else if (objc > 2) {
            tupleObjPush(interp,result,lazyObjNew(interp,NULL,objc-2,objv+1,cons->obj,NULL));
        } else {
            tupleObjPush(interp,result,cons->obj);
        }
    }
    Tcl_SetObjResult(interp,result);
    return TCL_OK;
}

int
foldObjCmd(ClientData data,Tcl_Interp *interp,int objc,Tcl_Obj *const objv[])
{
    Tcl_Obj *result;
    Cons *cons;
    
    (void)data;
    if (objc==1) {
        Tcl_SetObjResult(interp,tupleObjNew());
        return TCL_OK;
    } else if (objc==2) {
        Tcl_SetObjResult(interp,objv[1]);
        return TCL_OK;
    }
    Tcl_Obj *tupleObj;
    tupleObj=objv[objc-1];
    SHOULD(tupleObj!=NULL);
    if (tupleObj->typePtr==lazyType) {
        lazyObjExec(tupleObj,1);
    }
    SHOULD(tupleObj->typePtr==tupleType || Tcl_ConvertToType(interp,tupleObj,tupleType)==TCL_OK,return TCL_ERROR);
    if (tupleObjCompact(interp,tupleObj)!=TCL_OK) return TCL_ERROR;
    cons=consFirst(TUPLE(tupleObj)->head,NULL);
    if (cons==NULL) {
        Tcl_SetObjResult(interp,tupleObjNew());
        return TCL_OK;
    }
    if (cons->obj->typePtr==tupleType || cons->obj->typePtr==lazyType) {
        result=lazyObjNew(interp,NULL,objc-1,objv,cons->obj,NULL);
    } else {
        result=lazyObjNew(interp,NULL,objc-2,objv+1,cons->obj,NULL);
    }
    for(cons=cons->next;cons!=NULL;cons=cons->next) {
        if (cons->obj==NULL) continue;
        if (cons->obj->typePtr==tupleType || cons->obj->typePtr==lazyType) {
            result=lazyObjNew(interp,NULL,objc-2,objv,result,cons->obj,NULL);
        } else {
            result=lazyObjNew(interp,NULL,objc-3,objv+1,result,cons->obj,NULL);
        }
    }
    Tcl_SetObjResult(interp,result);
    return TCL_OK;
}

int
compactObjCmd(ClientData data,Tcl_Interp *interp,int objc,Tcl_Obj *const objv[])
{
    (void)data;
    if (objc!=2) {
        Tcl_WrongNumArgs(interp,objc,objv,"tupleObj");
        return TCL_ERROR;
    }
    if (tupleObjCompact(interp,objv[1])!=TCL_OK)
        return TCL_ERROR;
    Tcl_SetObjResult(interp,objv[1]);
    return TCL_OK;
}
